﻿using System;
using System.Collections.Generic;
using System.Linq;
using Framework.DataCache.Callback;
using Microsoft.ApplicationServer.Caching;

namespace Framework.DataCache
{
    public sealed class DistributedDataCache
    {
        private static readonly DataCacheFactory CacheFactory;

        private static readonly Dictionary<string, DataCacheLockHandle> DataCacheLockHandle;
        private static readonly Dictionary<string, DataCacheItemVersion> DataCacheItemVersions;

        private static readonly Dictionary<string, DistributedDataCacheNotificationCallback> RegionLevelCb;

        private static readonly Dictionary<string, DistributedDataCacheNotificationCallback> ItemLevelCb;

        private static readonly Dictionary<string, DistributedDataCacheFailureNotificationCallback> FailureNotificationCb;

        private static readonly Dictionary<string, DistributedDataCacheNotificationCallback> CacheLevelCb;

        private static readonly Dictionary<string, DistributedDataCacheBulkNotificationCallback> CacheLevelBulkCb;

        static DistributedDataCache()
        {
            CacheFactory = new DataCacheFactory();

            DataCacheLockHandle = new Dictionary<string, DataCacheLockHandle>();
            DataCacheItemVersions = new Dictionary<string, DataCacheItemVersion>();

            RegionLevelCb = new Dictionary<string, DistributedDataCacheNotificationCallback>();

            ItemLevelCb = new Dictionary<string, DistributedDataCacheNotificationCallback>();

            FailureNotificationCb = new Dictionary<string, DistributedDataCacheFailureNotificationCallback>();

            CacheLevelCb = new Dictionary<string, DistributedDataCacheNotificationCallback>();

            CacheLevelBulkCb = new Dictionary<string, DistributedDataCacheBulkNotificationCallback>();
        }

        private DistributedDataCache() { }

        #region [ Get ]

        public static T Get<T>(string cacheName, string key, string region = null)
        {
            object objectValue = Get(cacheName, key, region);
            if (objectValue == null)
                return default(T);
            if(objectValue.GetType()==typeof(T))
                return (T)objectValue;
            return default(T);
        }

        public static object Get(string cacheName, string key, string region = null)
        {
            var cache = CacheFactory.GetCache(cacheName);
            DataCacheItemVersion dataCacheItemVersion;
            object objectValue = string.IsNullOrEmpty(region) ? cache.Get(key, out dataCacheItemVersion) : cache.Get(key, out dataCacheItemVersion, region);
            SaveDataCacheItemVersion(cacheName, region, key, dataCacheItemVersion);

            return objectValue;
        }

        #endregion

        #region [ GetCacheItem ]

        public static DistributedDataCacheItem GetCacheItem(string cacheName, string key, string region = null)
        {
            var cache = CacheFactory.GetCache(cacheName);
            DataCacheItem item = string.IsNullOrEmpty(region) ? cache.GetCacheItem(key) : cache.GetCacheItem(key, region);
            var distributedDataCacheItem = new DistributedDataCacheItem(item);
            return distributedDataCacheItem;
        }

        #endregion

        #region [ BulkGet ]

        public static IEnumerable<KeyValuePair<string, object>> BulkGet(string cacheName, IEnumerable<string> key, string region)
        {
            var cache = CacheFactory.GetCache(cacheName);
            IEnumerable<KeyValuePair<string, object>> keyValuePairs = cache.BulkGet(key, region);
            return keyValuePairs;
        }

        #endregion

        #region [ GetAndLock ]

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cacheName"></param>
        /// <param name="timeOut"></param>
        /// <param name="region"></param>
        /// <param name="key"></param>
        /// <param name="forceLock">是否强制锁定</param>
        /// <returns></returns>
        public static object GetAndLock(string cacheName, TimeSpan timeOut, string key, bool forceLock = false, string region = null)
        {
            DataCacheLockHandle dataCacheLockHandle;
            var cache = CacheFactory.GetCache(cacheName);
            object objectValue = string.IsNullOrEmpty(region) ? cache.GetAndLock(key, timeOut, out dataCacheLockHandle, forceLock) : cache.GetAndLock(key, timeOut, out dataCacheLockHandle, region, forceLock);
            SaveDataCacheLockHandle(cacheName, region, key, dataCacheLockHandle);
            return objectValue;
        }

        #endregion

        #region [ Add ]

        public static void Add(string cacheName, string key, object value, string region = null, TimeSpan timeOut = default(TimeSpan))
        {
            var cache = CacheFactory.GetCache(cacheName);
            DataCacheItemVersion dataCacheItemVersion;

            if (string.IsNullOrEmpty(region) && timeOut == default(TimeSpan))
                dataCacheItemVersion = cache.Add(key, value);
            else if (string.IsNullOrEmpty(region))
                dataCacheItemVersion = cache.Add(key, value, timeOut);
            else if (timeOut == default(TimeSpan))
                dataCacheItemVersion = cache.Add(key, value, region);
            else
                dataCacheItemVersion = cache.Add(key, value, timeOut, region);

            SaveDataCacheItemVersion(cacheName, region, key, dataCacheItemVersion);
        }

        #endregion

        #region [ Put ]

        public static void Put(string cacheName, string key, object value, string region = null, TimeSpan outTime = default(TimeSpan), bool isVerifyItemVersion = false)
        {
            var cache = CacheFactory.GetCache(cacheName);
            bool flag = false;
            DataCacheItemVersion dataCacheItemVersion = null;
            if (isVerifyItemVersion)
            {
                if (DataCacheItemVersions.ContainsKey(key))
                {
                    if (string.IsNullOrEmpty(region) && outTime == default(TimeSpan))
                    {
                        dataCacheItemVersion = cache.Put(key, value, DataCacheItemVersions[key]);
                    }
                    else if (string.IsNullOrEmpty(region))
                    {
                        dataCacheItemVersion = cache.Put(key, value, DataCacheItemVersions[key], outTime);
                    }
                    else if (outTime == default(TimeSpan))
                    {
                        dataCacheItemVersion = cache.Put(key, value, DataCacheItemVersions[key], region);
                    }
                    else
                    {
                        dataCacheItemVersion = cache.Put(key, value, DataCacheItemVersions[key], outTime, region);
                    }
                }
                else
                {
                    flag = true;
                }
            }
            if (flag || !isVerifyItemVersion)
            {
                if (string.IsNullOrEmpty(region) && outTime == default(TimeSpan))
                {
                    dataCacheItemVersion = cache.Put(key, value);
                }
                else if (string.IsNullOrEmpty(region))
                {
                    dataCacheItemVersion = cache.Put(key, value, outTime);
                }
                else if (outTime == default(TimeSpan))
                {
                    dataCacheItemVersion = cache.Put(key, value, region);
                }
                else
                {
                    dataCacheItemVersion = cache.Put(key, value, outTime, region);
                }
            }
            SaveDataCacheItemVersion(cacheName, region, key, dataCacheItemVersion);
        }

        #endregion

        #region [ PutAndUnlock ]

        public static void PutAndUnlock(string cacheName, string key, object value, string region = null, TimeSpan timeOut = default(TimeSpan))
        {
            var cache = CacheFactory.GetCache(cacheName);
            if (string.IsNullOrEmpty(region) && timeOut == default(TimeSpan))
                cache.PutAndUnlock(key, value, GetDataCacheLockHandle(cacheName, region, key));
            else if (string.IsNullOrEmpty(region))
                cache.PutAndUnlock(key, value, GetDataCacheLockHandle(cacheName, region, key), timeOut);
            else if (timeOut == default(TimeSpan))
                cache.PutAndUnlock(key, value, GetDataCacheLockHandle(cacheName, region, key), region);
            else
                cache.PutAndUnlock(key, value, GetDataCacheLockHandle(cacheName, region, key), timeOut, region);
        }

        #endregion

        #region [ Callback ]

        #region [ AddCacheLevelBulkCallback ]

        private static bool _isCacheLevelBulkCallbackInit;
        private static DataCacheNotificationDescriptor _cacheLevelBulkNd;

        public static bool AddCacheLevelBulkCallback(string cacheName, DistributedDataCacheBulkNotificationCallback distributedDataCacheBulkNotificationCallback)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetCacheLevelBulkCallbackKey(cacheName);
            if (!CacheLevelBulkCb.ContainsKey(dictionaryKey))
            {
                if (_isCacheLevelBulkCallbackInit == false)
                {
                    _cacheLevelBulkNd = cache.AddCacheLevelBulkCallback(CacheLevelBulkCallback);
                    _isCacheLevelBulkCallbackInit = true;
                }
                CacheLevelBulkCb.Add(dictionaryKey, distributedDataCacheBulkNotificationCallback);
                return true;
            }
            return false;
        }

        static void CacheLevelBulkCallback(string cacheName, IEnumerable<DataCacheOperationDescriptor> operations, DataCacheNotificationDescriptor nd)
        {
            var list = new List<DistributedDataCacheOperationDescriptor>();
            if (operations != null)
            {
                list.AddRange(operations.Select(dataCacheOperationDescriptor => new DistributedDataCacheOperationDescriptor(dataCacheOperationDescriptor)));
            }
            string dictionaryKey = GetCacheLevelBulkCallbackKey(cacheName);
            CacheLevelBulkCb[dictionaryKey].Invoke(cacheName, list, new DistributedDataCacheNotificationDescriptor(nd));
        }

        private static string GetCacheLevelBulkCallbackKey(string cacheName)
        {
            return cacheName;
        }

        public static bool RemoveCacheLevelBulkCallback(string cacheName)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetCacheLevelBulkCallbackKey(cacheName);
            if (!CacheLevelBulkCb.ContainsKey(dictionaryKey))
                return false;
            CacheLevelBulkCb.Remove(dictionaryKey);
            if (CacheLevelBulkCb.Count <= 0)
                cache.RemoveCallback(_cacheLevelBulkNd);
            return true;
        }

        #endregion

        #region [ AddCacheLevelCallback ]

        private static bool _isCacheLevelCallbackInit;
        private static DataCacheNotificationDescriptor _cacheLevelNd;

        public static bool AddCacheLevelCallback(string cacheName, DistributedDataCacheOperations distributedDataCacheOperations,
            DistributedDataCacheNotificationCallback distributedDataCacheNotificationCallback)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetCacheLevelCallbackKey(cacheName);
            if (!CacheLevelCb.ContainsKey(dictionaryKey))
            {
                if (_isCacheLevelCallbackInit == false)
                {
                    _cacheLevelNd = cache.AddCacheLevelCallback((DataCacheOperations)distributedDataCacheOperations, CacheLevelCallback);
                    _isCacheLevelCallbackInit = true;
                }
                CacheLevelCb.Add(dictionaryKey, distributedDataCacheNotificationCallback);
                return true;
            }
            return false;
        }

        static void CacheLevelCallback(string cacheName, string regionName, string key,
            DataCacheItemVersion version, DataCacheOperations cacheOperation, DataCacheNotificationDescriptor nd)
        {
            string dictionaryKey = GetCacheLevelCallbackKey(cacheName);
            CacheLevelCb[dictionaryKey].Invoke(cacheName, regionName, key, new DistributedDataCacheItemVersion(version),
                (DistributedDataCacheOperations)cacheOperation, new DistributedDataCacheNotificationDescriptor(nd));
        }

        private static string GetCacheLevelCallbackKey(string cacheName)
        {
            return cacheName;
        }

        public static bool RemoveCacheLevelCallback(string cacheName)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetCacheLevelCallbackKey(cacheName);
            if (!CacheLevelCb.ContainsKey(dictionaryKey))
                return false;
            if (CacheLevelCb.Count <= 0)
                cache.RemoveCallback(_cacheLevelNd);
            CacheLevelCb.Remove(dictionaryKey);
            return true;
        }

        #endregion

        #region [ AddFailureNotificationCallback ]

        private static bool _isFailureNotificationCallbackInit;
        private static DataCacheNotificationDescriptor _failureNotificationNd;

        public static bool AddFailureNotificationCallback(string cacheName, DistributedDataCacheFailureNotificationCallback distributedDataCacheFailureNotificationCallback)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetFailureNotificationCallbackKey(cacheName);
            if (!FailureNotificationCb.ContainsKey(dictionaryKey))
            {
                if (_isFailureNotificationCallbackInit == false)
                {
                    _failureNotificationNd = cache.AddFailureNotificationCallback(DataCacheFailureNotificationCallback);
                    _isFailureNotificationCallbackInit = true;
                }
                FailureNotificationCb.Add(dictionaryKey, distributedDataCacheFailureNotificationCallback);
                return true;
            }
            return false;
        }

        static void DataCacheFailureNotificationCallback(string cacheName, DataCacheNotificationDescriptor nd)
        {
            string dictionaryKey = GetFailureNotificationCallbackKey(cacheName);
            FailureNotificationCb[dictionaryKey].Invoke(cacheName, new DistributedDataCacheNotificationDescriptor(nd));
        }

        private static string GetFailureNotificationCallbackKey(string cacheName)
        {
            return cacheName;
        }

        public static bool RemoveFailureNotificationCallback(string cacheName)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetFailureNotificationCallbackKey(cacheName);
            if (!FailureNotificationCb.ContainsKey(dictionaryKey))
                return false;
            FailureNotificationCb.Remove(dictionaryKey);
            if (FailureNotificationCb.Count <= 0)
                cache.RemoveCallback(_failureNotificationNd);
            return true;
        }

        #endregion

        #region [ AddItemLevelCallback ]

        private static bool _isItemLevelCallbackInit;
        private static DataCacheNotificationDescriptor _itemLevelNd;

        public static void AddItemLevelCallback(string cacheName, string key, DistributedDataCacheOperations distributedDataCacheOperations,
            DistributedDataCacheNotificationCallback distributedDataCacheNotificationCallback, string region = null)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetItemLevelCallbackKey(cacheName, key, distributedDataCacheOperations);
            if (!ItemLevelCb.ContainsKey(dictionaryKey))
            {
                if (_isItemLevelCallbackInit == false)
                {
                    _itemLevelNd = string.IsNullOrEmpty(region) ?
                    cache.AddItemLevelCallback(key, (DataCacheOperations)distributedDataCacheOperations, ItemLevelCallback) :
                    cache.AddItemLevelCallback(key, (DataCacheOperations)distributedDataCacheOperations, ItemLevelCallback, region);
                    _isItemLevelCallbackInit = true;
                }
                ItemLevelCb.Add(dictionaryKey, distributedDataCacheNotificationCallback);
            }
        }

        static void ItemLevelCallback(string cacheName, string regionName, string key,
            DataCacheItemVersion version, DataCacheOperations cacheOperation, DataCacheNotificationDescriptor nd)
        {
            string dictionaryKey = GetItemLevelCallbackKey(cacheName, key, (DistributedDataCacheOperations)cacheOperation);
            ItemLevelCb[dictionaryKey].Invoke(cacheName, regionName, key, new DistributedDataCacheItemVersion(version),
                (DistributedDataCacheOperations)cacheOperation, new DistributedDataCacheNotificationDescriptor(nd));
        }

        private static string GetItemLevelCallbackKey(string cacheName, string key, DistributedDataCacheOperations distributedDataCacheOperations)
        {
            return cacheName + key + Convert.ToInt32(distributedDataCacheOperations);
        }

        public static bool RemoveItemLevelCallback(string cacheName, string key, DistributedDataCacheOperations distributedDataCacheOperations)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetItemLevelCallbackKey(cacheName, key, distributedDataCacheOperations);
            if (!ItemLevelCb.ContainsKey(dictionaryKey))
                return false;
            ItemLevelCb.Remove(dictionaryKey);
            if (FailureNotificationCb.Count <= 0)
                cache.RemoveCallback(_itemLevelNd);
            return true;
        }

        #endregion

        #region [ AddRegionLevelCallback ]

        private static bool _isRegionLevelCallbackInit;
        private static DataCacheNotificationDescriptor _regionLevelNd;

        public static bool AddRegionLevelCallback(string cacheName, string region, DistributedDataCacheOperations distributedDataCacheOperations,
            DistributedDataCacheNotificationCallback distributedDataCacheNotificationCallback)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string key = GetRegionLevelCallbackKey(cacheName, region, distributedDataCacheOperations);
            if (!RegionLevelCb.ContainsKey(key))
            {
                if (_isRegionLevelCallbackInit == false)
                {
                    _regionLevelNd = cache.AddRegionLevelCallback(region,
                    (DataCacheOperations)distributedDataCacheOperations, RegionLevelCallback);
                    _isRegionLevelCallbackInit = true;
                }
                RegionLevelCb.Add(key, distributedDataCacheNotificationCallback);
                return true;
            }
            return false;
        }

        static void RegionLevelCallback(string cacheName, string regionName, string key,
            DataCacheItemVersion version, DataCacheOperations cacheOperation, DataCacheNotificationDescriptor nd)
        {
            string dictionaryKey = GetRegionLevelCallbackKey(cacheName, regionName, (DistributedDataCacheOperations)cacheOperation);
            RegionLevelCb[dictionaryKey].Invoke(cacheName, regionName, key, new DistributedDataCacheItemVersion(version),
                (DistributedDataCacheOperations)cacheOperation, new DistributedDataCacheNotificationDescriptor(nd));
        }

        private static string GetRegionLevelCallbackKey(string cacheName, string region, DistributedDataCacheOperations distributedDataCacheOperations)
        {
            return cacheName + region + Convert.ToInt32(distributedDataCacheOperations);
        }

        public static bool RemoveRegionLevelCallback(string cacheName, string region, DistributedDataCacheOperations distributedDataCacheOperations)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string dictionaryKey = GetRegionLevelCallbackKey(cacheName, region, distributedDataCacheOperations);
            if (!RegionLevelCb.ContainsKey(dictionaryKey))
                return false;
            RegionLevelCb.Remove(dictionaryKey);
            if (RegionLevelCb.Count <= 0)
                cache.RemoveCallback(_regionLevelNd);
            return true;
        }

        #endregion


        #endregion

        #region [ private ]

        private static void SaveDataCacheItemVersion(string cacheName, string region, string key, DataCacheItemVersion dataCacheItemVersion)
        {
            string dictionaryKey = cacheName + region + key;
            if (DataCacheItemVersions.ContainsKey(dictionaryKey))
                DataCacheItemVersions[dictionaryKey] = dataCacheItemVersion;
            else
                DataCacheItemVersions.Add(dictionaryKey, dataCacheItemVersion);
        }

        private static DataCacheItemVersion GetDataCacheItemVersion(string cacheName, string region, string key)
        {
            string dictionaryKey = cacheName + region + key;
            if (DataCacheItemVersions.ContainsKey(dictionaryKey))
                return DataCacheItemVersions[dictionaryKey];
            return null;
        }

        private static void SaveDataCacheLockHandle(string cacheName, string region, string key, DataCacheLockHandle dataCacheLockHandle)
        {
            string dictionaryKey = cacheName + region + key;
            if (DataCacheLockHandle.ContainsKey(dictionaryKey))
                DataCacheLockHandle[dictionaryKey] = dataCacheLockHandle;
            else
                DataCacheLockHandle.Add(dictionaryKey, dataCacheLockHandle);
        }

        private static DataCacheLockHandle GetDataCacheLockHandle(string cacheName, string region, string key)
        {
            string dictionaryKey = cacheName + region + key;
            if (DataCacheLockHandle.ContainsKey(dictionaryKey))
                return DataCacheLockHandle[dictionaryKey];
            return null;
        }

        #endregion

        #region [ Region ]

        public static bool CreateRegion(string cacheName, string regionName)
        {
            var cache = CacheFactory.GetCache(cacheName);
            return cache.CreateRegion(regionName);
        }

        public static IEnumerable<KeyValuePair<string, object>> GetObjectsInRegion(string cacheName, string regionName)
        {
            var cache = CacheFactory.GetCache(cacheName);
            IEnumerable<KeyValuePair<string, object>> keyValuePairs = cache.GetObjectsInRegion(regionName);
            return keyValuePairs;
        }

        public static void ClearRegion(string cacheName, string regionName)
        {
            var cache = CacheFactory.GetCache(cacheName);
            cache.ClearRegion(regionName);
        }

        public static string GetSystemRegionName(string cacheName, string key)
        {
            var cache = CacheFactory.GetCache(cacheName);
            string name = cache.GetSystemRegionName(key);
            return name;
        }

        public static IEnumerable<string> GetSystemRegions(string cacheName)
        {
            var cache = CacheFactory.GetCache(cacheName);
            IEnumerable<string> keys = cache.GetSystemRegions();
            return keys;
        }

        public static bool RemoveRegion(string cacheName, string regionName)
        {
            var cache = CacheFactory.GetCache(cacheName);
            bool flag = cache.RemoveRegion(regionName);
            return flag;
        }

        #endregion

        #region [ Tag ]

        public static IEnumerable<KeyValuePair<string, object>> GetObjectsByAllTags(string cacheName, IEnumerable<DistributedDataCacheTag> tags, string region)
        {
            var cache = CacheFactory.GetCache(cacheName);
            var list = tags.Select(dataCacheTag => dataCacheTag.DataCacheTag).ToList();
            IEnumerable<KeyValuePair<string, object>> keyValuePairs = cache.GetObjectsByAllTags(list, region);
            return keyValuePairs;
        }

        public static IEnumerable<KeyValuePair<string, object>> GetObjectsByAnyTag(string cacheName, IEnumerable<DistributedDataCacheTag> tags, string region)
        {
            var cache = CacheFactory.GetCache(cacheName);
            var list = tags.Select(dataCacheTag => dataCacheTag.DataCacheTag).ToList();
            var keyValuePairs = cache.GetObjectsByAnyTag(list, region);
            return keyValuePairs;
        }

        public static IEnumerable<KeyValuePair<string, object>> GetObjectsByTag(string cacheName, DistributedDataCacheTag tag, string region)
        {
            var cache = CacheFactory.GetCache(cacheName);
            IEnumerable<KeyValuePair<string, object>> keyValuePairs = cache.GetObjectsByTag(tag.DataCacheTag, region);
            return keyValuePairs;
        }

        #endregion

        #region [ Unlock ]

        public static void Unlock(string cacheName, string key, TimeSpan outTime = default(TimeSpan), string region = null)
        {
            DataCacheLockHandle dataCacheLockHandle = GetDataCacheLockHandle(cacheName, region, key);
            if (dataCacheLockHandle == null)
                throw new NullReferenceException("未找到锁定！");
            var cache = CacheFactory.GetCache(cacheName);
            if (string.IsNullOrEmpty(region) && outTime == default(TimeSpan))
                cache.Unlock(key, dataCacheLockHandle);
            else if (string.IsNullOrEmpty(region))
                cache.Unlock(key, dataCacheLockHandle, outTime);
            else if (outTime == default(TimeSpan))
                cache.Unlock(key, dataCacheLockHandle, region);
            else
                cache.Unlock(key, dataCacheLockHandle, outTime, region);
        }

        #endregion

        #region [ ResetObjectTimeout ]

        public static void ResetObjectTimeout(string cacheName, string key, TimeSpan timeSpan, string region = null)
        {
            var cache = CacheFactory.GetCache(cacheName);
            cache.ResetObjectTimeout(key, timeSpan, region);
        }

        #endregion

        #region [ Remove ]

        public static bool Remove(string cacheName, string key, string region = null)
        {
            var cache = CacheFactory.GetCache(cacheName);
            bool flag = cache.Remove(key, region);
            return flag;
        }

        #endregion

        #region [ GetIfNewer ]

        public static object GetIfNewer(string cacheName, string key, string region = null)
        {
            DataCacheItemVersion dataCacheItemVersion = GetDataCacheItemVersion(cacheName, region, key);
            if (dataCacheItemVersion == null)
                throw new NullReferenceException("缓存版本不存在");
            var cache = CacheFactory.GetCache(cacheName);
            object objectValue = string.IsNullOrEmpty(region) ? cache.GetIfNewer(key, ref dataCacheItemVersion) : cache.GetIfNewer(key, ref dataCacheItemVersion, region);
            return objectValue;
        }

        #endregion
    }
}
